package cn.jetpiece.cloud.joss.sdk.core.oss;

import cn.jetpiece.cloud.joss.sdk.config.properties.OssApiProperties;
import cn.jetpiece.cloud.joss.sdk.core.exception.OssExceptionEnum;
import cn.jetpiece.cloud.joss.sdk.core.exception.OssSdkException;
import cn.jetpiece.cloud.joss.sdk.core.oss.model.JossPartEtag;
import cn.jetpiece.cloud.joss.sdk.core.oss.model.OssSdkBaseResult;
import cn.jetpiece.cloud.joss.sdk.core.oss.model.PutObject;
import cn.jetpiece.cloud.joss.sdk.core.oss.model.PutObjectResult;
import cn.jetpiece.cloud.joss.sdk.core.oss.model.enums.PutObjectModeEnum;
import cn.jetpiece.cloud.joss.sdk.core.util.CommonInputStreamResource;
import cn.jetpiece.cloud.joss.sdk.core.util.RestUtils;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.File;
import java.io.InputStream;
import java.util.List;

/**
 * @author XuJZ
 * @since 2021/1/19
 */
public class OssService {
    private final Logger log = LoggerFactory.getLogger(OssService.class);
    private OssApiProperties ossApiProperties;
    private RestUtils restUtils;

    /**
     * 拼接Key /{bucketName}/{objectName}
     *
     * @param bucketName 桶名
     * @param objectName 对象名
     * @return 拼接结果
     */
    private static String getUrl(String bucketName, String objectName) {
        String key = bucketName;
        if (!key.startsWith("/")) {
            key = "/" + key;
        }
        if (key.endsWith("/")) {
            key = key.substring(0, key.length() - 1);
        }
        if (objectName.startsWith("/")) {
            key = key + objectName;
        } else {
            key = key + "/" + objectName;
        }
        return "/joss" + key;
    }

    public void setOssApiProperties(OssApiProperties ossApiProperties) {
        this.ossApiProperties = ossApiProperties;
    }

    public void setRestUtils(RestUtils restUtils) {
        this.restUtils = restUtils;
    }

    /**
     * 通过Stream上传
     *
     * @param objectName  对象名
     * @param inputStream 地址
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByStream(InputStream inputStream, String objectName) {
        return uploadByStream(inputStream, objectName, null);
    }

    /**
     * 通过Stream上传
     *
     * @param inputStream 地址
     * @param objectName  对象名
     * @param bucketName  桶id
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByStream(InputStream inputStream, String objectName, String bucketName) {
        return uploadByStream(inputStream, objectName, bucketName, null);
    }

    /**
     * 通过Stream上传
     *
     * @param inputStream 地址
     * @param objectName  对象名
     * @param bucketName  桶id
     * @param acl         数据权限(1:私有,2:公共可读),没传使用桶策略,Minio对象无法设置
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByStream(InputStream inputStream, String objectName, String bucketName, Integer acl) {
        PutObject request = new PutObject();
        request.setBucketName(bucketName);
        request.setAcl(acl);
        request.setModel(PutObjectModeEnum.FILE_STREAM.getCode());
        setRequestBaseParam(request);
        MultiValueMap<String, String> queryParams = request.queryParams();
        return restUtils.exchange(getUrl(request.getBucketName(), objectName), HttpMethod.PUT, queryParams, inputStream, PutObjectResult.class);
    }

    /**
     * 文件上传
     *
     * @param file       文件
     * @param objectName 对象名
     * @param bucketName 桶id
     * @param acl        数据权限(1:私有,2:公共可读),没传使用桶策略,Minio对象无法设置
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByFile(File file, String objectName, String bucketName, Integer acl) {
        PutObject request = new PutObject();
        request.setBucketName(bucketName);
        request.setAcl(acl);
        request.setModel(PutObjectModeEnum.FILE_STREAM.getCode());
        setRequestBaseParam(request);
        MultiValueMap<String, String> queryParams = request.queryParams();
        return restUtils.exchange(getUrl(request.getBucketName(), objectName), HttpMethod.PUT, queryParams, file, PutObjectResult.class);
    }

    /**
     * 通过Base64上传
     *
     * @param objectName 对象名
     * @param base64     地址
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByBase64(String base64, String objectName) {
        return uploadByBase64(base64, objectName, null);
    }

    /**
     * 通过Base64上传
     *
     * @param base64     地址
     * @param objectName 对象名
     * @param bucketName 桶id
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByBase64(String base64, String objectName, String bucketName) {
        return uploadByBase64(base64, objectName, bucketName, null);
    }

    /**
     * 通过Base64上传
     *
     * @param base64     地址
     * @param objectName 对象名
     * @param bucketName 桶id
     * @param acl        数据权限(1:私有,2:公共可读),没传使用桶策略,Minio对象无法设置
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByBase64(String base64, String objectName, String bucketName, Integer acl) {
        PutObject request = new PutObject();
        request.setBucketName(bucketName);
        request.setAcl(acl);
        request.setModel(PutObjectModeEnum.BASE64.getCode());
        setRequestBaseParam(request);
        MultiValueMap<String, String> queryParams = request.queryParams();
        return restUtils.exchange(getUrl(request.getBucketName(), objectName), HttpMethod.PUT, queryParams, base64, PutObjectResult.class);
    }

    /**
     * 通过Url上传
     *
     * @param url        地址
     * @param objectName 对象名
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByUrl(String url, String objectName) {
        return uploadByUrl(url, objectName, null);
    }

    /**
     * 通过Url上传
     *
     * @param url        地址
     * @param objectName 对象名
     * @param bucketName 桶id
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByUrl(String url, String objectName, String bucketName) {
        return uploadByUrl(url, objectName, bucketName, null);
    }

    /**
     * 通过Url上传
     *
     * @param url        地址
     * @param objectName 对象名
     * @param bucketName 桶id
     * @param acl        数据权限(1:私有,2:公共可读),没传使用桶策略,Minio对象无法设置
     * @return 结果
     */
    public OssSdkBaseResult<PutObjectResult> uploadByUrl(String url, String objectName, String bucketName, Integer acl) {
        PutObject request = new PutObject();
        request.setBucketName(bucketName);
        request.setAcl(acl);
        request.setModel(PutObjectModeEnum.URL.getCode());
        setRequestBaseParam(request);
        MultiValueMap<String, String> queryParams = request.queryParams();
        return restUtils.exchange(getUrl(request.getBucketName(), objectName), HttpMethod.PUT, queryParams, url, PutObjectResult.class);
    }

    /**
     * 获取访问路径前缀
     *
     * @return url
     */
    public String getBaseUrlPrefix() {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        restUtils.checkBaseParam();
        restUtils.buildSign(queryParams);
        try {
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<Object> entity = new HttpEntity<>(httpHeaders);
            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(getServerAddr() + "/oss/configs/base-url-prefix").queryParams(queryParams);
            ResponseEntity<String> responseEntity = RestUtils.REST_TEMPLATE.exchange(builder.build().toUri(), HttpMethod.GET, entity, String.class);
            String response = responseEntity.getBody();
            if (response == null) {
                throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
            }
            return response;
        } catch (RestClientException e) {
            log.error(e.getMessage(), e);
            throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
        }
    }

    /**
     * 获取临时访问JossToken
     *
     * @param bucketName 桶
     * @param path       对象名称
     * @param preExpires 预签名超时时间,单位:秒,默认6分钟
     * @return jossToken
     */
    public OssSdkBaseResult<PutObjectResult> getJossToken(String bucketName, String path, Long preExpires) {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        bucketName = setBucketId(bucketName);
        queryParams.add("preSign", "");
        if (path == null || path.length() < 1) {
            throw new OssSdkException(400, "参数[path]不能为空");
        }
        queryParams.add("path", path);
        if (preExpires != null) {
            queryParams.add("preExpires", preExpires.toString());
        }
        restUtils.checkBaseParam();
        restUtils.buildSign(queryParams);
        try {
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<Object> entity = new HttpEntity<>(httpHeaders);
            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(getServerAddr() + getUrl(bucketName, path)).queryParams(queryParams);
            ResponseEntity<String> responseEntity = RestUtils.REST_TEMPLATE.exchange(builder.build().toUri(), HttpMethod.POST, entity, String.class);
            String response = responseEntity.getBody();
            if (response == null) {
                throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
            }
            return JSONObject.parseObject(response, new TypeReference<OssSdkBaseResult<PutObjectResult>>() {
            });
        } catch (RestClientException e) {
            log.error(e.getMessage(), e);
            throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
        }
    }

    /**
     * 初始化分片上传
     *
     * @param bucketName  桶
     * @param objectName  对象名称
     * @param contentType 内容类型
     * @param acl         数据权限(1:私有,2:公共可读),没传使用桶策略,Minio对象无法设置
     * @param expires     分片上传任务最终生成对象的过期时间，正整数。默认30分钟
     * @return uploadId
     */
    public OssSdkBaseResult<PutObjectResult> initMultipartUpload(String bucketName, String objectName, String contentType, Integer acl, Integer expires) {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        bucketName = setBucketId(bucketName);
        acl = setAcl(acl);
        queryParams.add("uploads", "");
        queryParams.add("bucketName", bucketName);
        if (contentType != null && contentType.length() != 0) {
            queryParams.add("contentType", contentType);
        }
        if (acl != null) {
            queryParams.add("acl", acl.toString());
        }
        if (expires != null) {
            queryParams.add("expires", expires.toString());
        }
        restUtils.checkBaseParam();
        restUtils.buildSign(queryParams);
        try {
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<Object> entity = new HttpEntity<>(httpHeaders);
            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(getServerAddr() + getUrl(bucketName, objectName)).queryParams(queryParams);
            ResponseEntity<String> responseEntity = RestUtils.REST_TEMPLATE.exchange(builder.build().toUri(), HttpMethod.POST, entity, String.class);
            String response = responseEntity.getBody();
            if (response == null) {
                throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
            }
            return JSONObject.parseObject(response, new TypeReference<OssSdkBaseResult<PutObjectResult>>() {
            });
        } catch (RestClientException e) {
            log.error(e.getMessage(), e);
            throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
        }
    }

    /**
     * 分片上传
     *
     * @param bucketName  桶
     * @param objectName  对象名称
     * @param uploadId    上传ID
     * @param partNumber  分片号，范围是1~10000
     * @param partSize    分片大小，单位：字节，最小100KB，最大10M
     * @param offset      偏移量，首个分片为0
     * @param contentMd5  分片MD5值
     * @param inputStream 流
     * @return 分片结果
     */
    public OssSdkBaseResult<PutObjectResult> multipartUpload(String bucketName, String objectName, String uploadId, Integer partNumber, Long partSize, Long offset, String contentMd5, InputStream inputStream) {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        bucketName = setBucketId(bucketName);
        queryParams.add("bucketName", bucketName);

        if (uploadId == null || uploadId.length() < 1) {
            throw new OssSdkException(400, "参数[uploadId]不能为空");
        }
        queryParams.add("uploadId", uploadId);

        if (partNumber == null) {
            throw new OssSdkException(400, "参数[uploadId]不能为空");
        }
        queryParams.add("partNumber", partNumber.toString());

        if (partSize == null) {
            throw new OssSdkException(400, "参数[partSize]不能为空");
        }
        queryParams.add("partSize", partSize.toString());

        if (offset == null) {
            throw new OssSdkException(400, "参数[offset]不能为空");
        }
        queryParams.add("offset", offset.toString());
        if (contentMd5 != null) {
            queryParams.add("contentMd5", contentMd5);
        }
        restUtils.checkBaseParam();
        restUtils.buildSign(queryParams);
        try {
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
            httpHeaders.setContentLength(partSize);
            MultiValueMap<String, Object> bodyParams = new LinkedMultiValueMap<>();
            Resource resource = new CommonInputStreamResource(inputStream);
            bodyParams.add("file", resource);
            HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(bodyParams, httpHeaders);
            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(getServerAddr() + getUrl(bucketName, objectName)).queryParams(queryParams);
            ResponseEntity<String> responseEntity = RestUtils.REST_TEMPLATE.exchange(builder.build().toUri(), HttpMethod.PUT, entity, String.class);
            String response = responseEntity.getBody();
            if (response == null) {
                throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
            }
            return JSONObject.parseObject(response, new TypeReference<OssSdkBaseResult<PutObjectResult>>() {
            });
        } catch (RestClientException e) {
            log.error(e.getMessage(), e);
            throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
        }

    }

    /**
     * 合并分片上传
     *
     * @param bucketName 桶
     * @param objectName 对象名称
     * @param uploadId   上传ID
     * @param etag       分片标记
     * @return 分片结果
     */
    public OssSdkBaseResult<PutObjectResult> completeMultipartUpload(String bucketName, String objectName, String uploadId, List<JossPartEtag> etag) {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        bucketName = setBucketId(bucketName);
        queryParams.add("bucketName", bucketName);

        if (uploadId == null || uploadId.length() < 1) {
            throw new OssSdkException(400, "参数[uploadId]不能为空");
        }
        queryParams.add("uploadId", uploadId);
        if (etag == null || etag.isEmpty()) {
            throw new OssSdkException(400, "参数[etag]不能为空");
        }
        restUtils.checkBaseParam();
        restUtils.buildSign(queryParams);
        try {
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<Object> entity = new HttpEntity<>(etag, httpHeaders);
            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(getServerAddr() + getUrl(bucketName, objectName)).queryParams(queryParams);
            ResponseEntity<String> responseEntity = RestUtils.REST_TEMPLATE.exchange(builder.build().toUri(), HttpMethod.POST, entity, String.class);
            String response = responseEntity.getBody();
            if (response == null) {
                throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
            }
            return JSONObject.parseObject(response, new TypeReference<OssSdkBaseResult<PutObjectResult>>() {
            });
        } catch (RestClientException e) {
            log.error(e.getMessage(), e);
            throw new OssSdkException(OssExceptionEnum.ACCESS_OSS_ERROR);
        }
    }

    /**
     * 删除对象
     *
     * @param url 对象名
     */
    public void deleteObjectByUrl(String url) {
        String key = url;
        if (url.contains("/oss/getObject")) {
            key = key.substring(key.indexOf("/oss/getObject") + "/oss/getObject".length());
        } else if (url.contains("/getObject")) {
            key = key.substring(key.indexOf("/getObject") + "/getObject".length());
        } else if (url.contains("/joss")) {
            key = key.substring(key.indexOf("/joss") + "/joss".length());
        }
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        restUtils.delete(key, queryParams);
    }

    /**
     * 删除对象
     *
     * @param bucketName 桶名
     * @param objectName 对象名
     */
    public void deleteObject(String bucketName, String objectName) {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        restUtils.delete(getUrl(setBucketId(bucketName), objectName), queryParams);
    }

    /**
     * 删除对象
     *
     * @param objectName 对象名
     */
    public void deleteObject(String objectName) {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        restUtils.delete(getUrl(ossApiProperties.getDefaultBucketName(), objectName), queryParams);
    }

    private String setBucketId(String bucketName) {
        if (bucketName == null || bucketName.length() < 1) {
            Assert.notNull(ossApiProperties.getDefaultBucketName(), "桶不能为空！");
            return ossApiProperties.getDefaultBucketName();
        }
        return bucketName;
    }

    private Integer setAcl(Integer acl) {
        if (acl != null) {
            return acl;
        }
        if (ossApiProperties.getDefaultAcl() != null) {
            return ossApiProperties.getDefaultAcl();
        }
        return 1;
    }

    private void setRequestBaseParam(PutObject request) {
        request.setBucketName(setBucketId(request.getBucketName()));
        request.setAcl(setAcl(request.getAcl()));
    }

    public String getServerAddr() {
        if (ossApiProperties.getServerAddr().endsWith("/joss")) {
            return ossApiProperties.getServerAddr().substring(0, ossApiProperties.getServerAddr().length() - 5);
        } else if (ossApiProperties.getServerAddr().endsWith("/oss")) {
            return ossApiProperties.getServerAddr().substring(0, ossApiProperties.getServerAddr().length() - 4);
        }
        return ossApiProperties.getServerAddr();
    }

}
